/*
    *@brief				:select()可用于执行 I/O 多路复用操作，调用 select()会一直阻塞，直到某一个或多个文件描述符成为就绪态（可以读或写）

    *@param[nfds]		:nfds 通常表示最大文件描述符编号值加 1，考虑 readfds、writefds 以及 exceptfds这三个文件描述符集合，在 3 个描述符集中找出最大描述符编号值，然后加 1，这就是参数 nfds。

    *@param[readfds]    :检测读是否就绪（是否可读）的文件描述符集合
    *@param[writefds]   :检测写是否就绪（是否可写）的文件描述符集合；
    *@param[exceptfds]  :检测异常情况是否发生的文件描述符集合。
    *@param[timeout]    :对 readfds、 writefds 以及 exceptfds 中的某些事件不感兴趣，可将其设置为 NULL，这表示对相应条件不关心。
                        如果这三个参数都设置为 NULL，则可以将 select()当做为一个类似于 sleep()休眠的函数来使用，通过 select()函数的最后一个参数 timeout 来设置休眠时间。

                        timeout 参数设置为 NULL，表示 select()将会一直阻塞、直到某一个或多个文件描述符成为就绪态；

    *@return 			:返回-1 表示有错误发生，并且会设置 errno。
                        返回 0 表示在任何文件描述符成为就绪态之前 select()调用已经超时， 在这种情况下， readfds，writefds 以及 exceptfds 所指向的文件描述符集合都会被清空。
                        返回一个正整数表示有一个或多个文件描述符已达到就绪态。

    *@others			:fd_set 类型指针，指向一个 fd_set 类型对象， fd_set 数据类型是一个文件描述符的集合体，所以参数 readfds、 writefds 以及exceptfds 都是指向文件描述符集合的指针.
*/

/*
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    //文件描述符操作宏
    void FD_CLR(int fd, fd_set *set);
    int FD_ISSET(int fd, fd_set *set);
    void FD_SET(int fd, fd_set *set);
    void FD_ZERO(fd_set *set);

    FD_ZERO()将参数 set 所指向的集合初始化为空；
    FD_SET()将文件描述符 fd 添加到参数 set 所指向的集合中；
    FD_CLR()将文件描述符 fd 从参数 set 所指向的集合中移除；
    如果文件描述符 fd 是参数 set 所指向的集合中的成员，则 FD_ISSET()返回 true，否则返回 false。
    文件描述符集合有一个最大容量限制，有常量 FD_SETSIZE 来决定，在 Linux 系统下，该常量的值为
    1024。在定义一个文件描述符集合之后，必须用 FD_ZERO()宏将其进行初始化操作，然后再向集合中添加
    我们关心的各个文件描述符，

    实例：
    fd_set fset; //定义文件描述符集合
    FD_ZERO(&fset); //将集合初始化为空
    FD_SET(3, &fset); //向集合中添加文件描述符 3
    FD_SET(4, &fset); //向集合中添加文件描述符 4
    FD_SET(5, &fset); //向集合中添加文件描述符 5
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>

#define MOUSE "/dev/input/event2"

int main(void)
{
    char buf[100];
    int fd, ret = 0, flag;
    fd_set rdfds;
    int loops = 5;

    /* 打开鼠标设备文件 */
    fd = open(MOUSE, O_RDONLY | O_NONBLOCK);
    if (-1 == fd)
    {
        perror("open error");
        exit(-1);
    }

    /* 将键盘设置为非阻塞方式 */
    flag = fcntl(0, F_GETFL); //先获取原来的 flag
    flag |= O_NONBLOCK;       //将 O_NONBLOCK 标准添加到 flag
    fcntl(0, F_SETFL, flag);  //重新设置 flag

    /* 同时读取键盘和鼠标 */
    while (loops--)
    {
        FD_ZERO(&rdfds);
        FD_SET(0, &rdfds);  //添加键盘
        FD_SET(fd, &rdfds); //添加鼠标

        ret = select(fd + 1, &rdfds, NULL, NULL, NULL);
        if (0 > ret)
        {
            perror("select error");
            goto out;
        }

        else if (0 == ret)
        {
            fprintf(stderr, "select timeout.\n");
            continue;
        }

        /* 检查键盘是否为就绪态 */
        if (FD_ISSET(0, &rdfds))
        {
            ret = read(0, buf, sizeof(buf));
            if (0 < ret)
                printf("键盘: 成功读取<%d>个字节数据\n", ret);
        }

        /* 检查鼠标是否为就绪态 */
        if (FD_ISSET(fd, &rdfds))
        {
            ret = read(fd, buf, sizeof(buf));
            if (0 < ret)
                printf("鼠标: 成功读取<%d>个字节数据\n", ret);
        }
    }

out:
    /* 关闭文件 */
    close(fd);
    exit(ret);
}